/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils, LineColPosition } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'AwaitThenableCheck');
const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/await-thenable-check.md",
    description: "Disallow awaiting a value that is not a Thenable.",
};
// 定义一个接口，用于存储问题的行列信息
interface LocationInfo {
    fileName: string;
    line: number;
    character: number;
}
export class AwaitThenableCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcherCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [fileMatcherCb];
    }

    public check = (arkFile: ArkFile) => {
        if (arkFile instanceof ArkFile) {
            const code = arkFile.getCode();
            if (!code) {
                return;
            }
            const filePath = arkFile.getFilePath();
            const sourceFile = AstTreeUtils.getASTNode(arkFile.getName(), code);
            for (let child of sourceFile.statements) {

                if (ts.isClassDeclaration(child)) {
                    this.processClassAST(child, sourceFile, arkFile);
                }
                if (ts.isFunctionDeclaration(child)) {
                    this.processFunctionAST(child, sourceFile, arkFile);
                }
                if (ts.isVariableDeclarationList(child)) {
                    for (let declaration of child.declarations) {
                        if (ts.isVariableDeclaration(declaration)) {
                            this.processVariableAST(declaration, sourceFile, arkFile);
                        }
                    }
                }
                if (ts.isExpressionStatement(child)) {
                    let expression = child.expression;
                    if (expression && ts.isAwaitExpression(expression)) {
                        let unaryExpression = expression.expression;
                        // await 常量
                        if (unaryExpression && (ts.isNumericLiteral(unaryExpression) || ts.isBigIntLiteral(unaryExpression) || ts.isStringLiteral(unaryExpression))) {
                            const originTsPosition = LineColPosition.buildFromNode(expression, sourceFile);
                            this.reportIssue(arkFile, originTsPosition, 'await');
                        }
                        // await 常量函数
                        if (unaryExpression && (ts.isCallExpression(unaryExpression))) {
                            let callExpression = unaryExpression.expression;
                            if (ts.isIdentifier(callExpression) && !this.isIdentifierHasAsyncKeywordVariableAST(sourceFile, callExpression.escapedText.toString())) {
                                const originTsPosition = LineColPosition.buildFromNode(expression, sourceFile);
                                this.reportIssue(arkFile, originTsPosition, 'await');
                            }
                        }
                    }
                }
            }

        }
    }

    private processClassAST(child: ts.ClassDeclaration, sourceFile: ts.SourceFile, arkFile: ArkFile): void {
        for (let nodeChild of child.members) {
            if (ts.isMethodDeclaration(nodeChild)) {
                this.processFunctionAST(nodeChild, sourceFile, arkFile);
            }
        }
    }

    private processVariableAST(declaration: ts.VariableDeclaration, sourceFile: ts.SourceFile, arkFile: ArkFile): void {
        if (ts.isIdentifier(declaration.name)) {
            const initializer = declaration.initializer;
            if (initializer && ts.isAwaitExpression(initializer)) {
                let unaryExpression = initializer.expression;
                // await 常量
                if (unaryExpression && (ts.isNumericLiteral(unaryExpression) || ts.isBigIntLiteral(unaryExpression) || ts.isStringLiteral(unaryExpression))) {
                    const originTsPosition = LineColPosition.buildFromNode(initializer, sourceFile);
                    this.reportIssue(arkFile, originTsPosition, 'await');
                }
                // await 常量函数
                if (unaryExpression && (ts.isCallExpression(unaryExpression))) {
                    let callExpression = unaryExpression.expression;
                    if (ts.isIdentifier(callExpression) && !this.isIdentifierHasAsyncKeywordVariableAST(sourceFile, callExpression.escapedText.toString())) {
                        const originTsPosition = LineColPosition.buildFromNode(initializer, sourceFile);
                        this.reportIssue(arkFile, originTsPosition, 'await');
                    }
                }
            }
        }
    }

    private processFunctionAST(child: ts.FunctionDeclaration | ts.MethodDeclaration, sourceFile: ts.SourceFile, arkFile: ArkFile): void {
        for (let stmtNode of child.body?.statements ?? []) {
            //找到await表达式
            if (!ts.isExpressionStatement(stmtNode)) {
                continue;
            }
            let expression = stmtNode.expression;
            if (expression && ts.isAwaitExpression(expression)) {
                let unaryExpression = expression.expression;

                //await 常量
                if (unaryExpression && (ts.isNumericLiteral(unaryExpression) || ts.isBigIntLiteral(unaryExpression) || ts.isStringLiteral(unaryExpression))) {
                    const originTsPosition = LineColPosition.buildFromNode(expression, sourceFile);
                    this.reportIssue(arkFile, originTsPosition, 'await');
                }
                //await 常量函数
                if (unaryExpression && (ts.isCallExpression(unaryExpression))) {
                    let callExpression = unaryExpression.expression;
                    if (ts.isIdentifier(callExpression) && !this.isIdentifierHasAsyncKeyword(child, callExpression.escapedText.toString())) {
                        const originTsPosition = LineColPosition.buildFromNode(expression, sourceFile);
                        this.reportIssue(arkFile, originTsPosition, 'await');
                    } else {
                        const issues = this.checkAwaitThenable(sourceFile, arkFile.getFilePath());
                        // 输出结果
                        issues.forEach(info => {
                            this.addIssueReportNode(info.line, info.character, arkFile.getFilePath());
                        });
                    }
                }
            }
        }
    }

    private checkAwaitThenable(sourceFile: ts.SourceFile, filePath: string): LocationInfo[] {
        const result: LocationInfo[] = [];
        const visit: (node: ts.Node) => void = (node: ts.Node) => {
            // 检查当前节点是否是 AwaitExpression
            if (ts.isAwaitExpression(node)) {
                const awaitedExpression = node.expression;

                // 检查是否为 Promise 或 thenable 对象
                if (!this.isPossiblePromiseOrThenable(awaitedExpression, sourceFile)) {
                    // 如果不是 Promise 或 thenable 对象，记录位置
                    const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
                    result.push({
                        fileName: sourceFile.fileName,
                        line: line + 1, // 行号从1开始
                        character: character + 1, // 列号从1开始
                    });
                }
            }
            // 递归遍历子节点
            ts.forEachChild(node, visit);
        };

        visit(sourceFile);
        return result;
    }
    private isPossiblePromiseOrThenable(node: ts.Node, sourceFile: ts.SourceFile): boolean {
        // 如果是直接量（如字符串、数字等），不是 Promise 或 thenable 对象
        if (ts.isLiteralExpression(node)) {
            return false;
        }

        // 如果是函数调用，可能是 Promise 或 thenable 对象
        if (ts.isCallExpression(node)) {
            // 检查是否是 Promise.resolve 或 Promise.reject 调用
            if (
                ts.isPropertyAccessExpression(node.expression) &&
                ts.isIdentifier(node.expression.name) &&
                (node.expression.name.text === 'resolve' || node.expression.name.text === 'reject') &&
                ts.isIdentifier(node.expression.expression) &&
                node.expression.expression.text === 'Promise'
            ) {
                return true;
            }

            // 检查是否是 async 函数调用
            if (this.isAsyncFunction(node.expression, sourceFile)) {
                return true;
            }
        }

        // 如果是 Promise 构造函数调用，如 new Promise(...)
        if (
            ts.isNewExpression(node) &&
            ts.isIdentifier(node.expression) &&
            node.expression.text === 'Promise'
        ) {
            return true;
        }

        // 如果是对象字面量，且包含 then 属性，可能是 thenable 对象
        if (ts.isObjectLiteralExpression(node)) {
            for (const prop of node.properties) {
                if (
                    ts.isPropertyAssignment(prop) &&
                    ts.isIdentifier(prop.name) &&
                    prop.name.text === 'then'
                ) {
                    return true;
                }
            }
        }

        // 如果是变量引用，无法准确判断，假设可能是 Promise 或 thenable 对象
        if (ts.isIdentifier(node)) {
            return true;
        }

        // 其他情况，认为不是 Promise 或 thenable 对象
        return false;
    }

    private isAsyncFunction(expression: ts.Expression, sourceFile: ts.SourceFile): boolean {
        // 检查是否是函数声明或函数表达式
        if (!ts.isIdentifier(expression)) {
            return false;
        }
        // 遍历源文件中的所有声明
        for (const statement of sourceFile.statements) {
            // 检查是否是函数声明
            if (ts.isFunctionDeclaration(statement) && statement.name && ts.isIdentifier(statement.name) &&
                statement.name.text === expression.text) {
                return statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || false;
            }

            // 检查是否是变量声明
            if (!ts.isVariableStatement(statement)) {
                continue;
            }
            for (const declaration of statement.declarationList.declarations) {
                // 检查是否是函数表达式
                if (declaration.initializer && ts.isFunctionExpression(declaration.initializer) &&
                    ts.isIdentifier(declaration.name) && declaration.name.text === expression.text) {
                    return declaration.initializer.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) || false;
                }
            }

        }
        return false;
    }
    private isIdentifierHasAsyncKeywordVariableAST(sourceFile: ts.SourceFile, identifier: string): boolean {
        for (let stmtNode of sourceFile?.statements ?? []) {
            if (!ts.isVariableStatement(stmtNode)) {
                continue;
            }
            let variableList = stmtNode.declarationList;
            if (variableList && variableList.declarations) {
                for (let declaration of variableList.declarations) {
                    if (!ts.isVariableDeclaration(declaration)) {
                        continue;
                    }
                    let nameIdentifier = declaration.name;
                    if (ts.isIdentifier(nameIdentifier) && nameIdentifier.escapedText === identifier) {
                        let initializer = declaration.initializer;
                        if (initializer && ts.isArrowFunction(initializer)) {
                            let modifiers = initializer.modifiers;
                            if (!modifiers || modifiers.length === 0) {
                                return false;
                            }
                            for (let modifier of modifiers) {
                                if (modifier.kind === ts.SyntaxKind.AsyncKeyword) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    private isIdentifierHasAsyncKeyword(child: ts.FunctionDeclaration | ts.MethodDeclaration, identifier: string): boolean {
        // 检查函数声明是否有 async 修饰符
        if (child.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
            return true;
        }

        for (let stmtNode of child.body?.statements ?? []) {
            if (!ts.isVariableStatement(stmtNode)) {
                continue;
            }
            return this.stmtNodeHasAsyncKeyword(stmtNode, identifier);
        }

        return false;
    }

    private stmtNodeHasAsyncKeyword(stmtNode: ts.VariableStatement, identifier: string): boolean {
        let variableList = stmtNode.declarationList;
        if (variableList && variableList.declarations) {
            for (let declaration of variableList.declarations) {
                if (!ts.isVariableDeclaration(declaration)) {
                    continue;
                }
                let nameIdentifier = declaration.name;
                if (ts.isIdentifier(nameIdentifier) && nameIdentifier.escapedText === identifier) {
                    return this.identifierHasAsyncKeyword(declaration, identifier);
                }
            }
        }
        return false;
    }

    private identifierHasAsyncKeyword(declaration: ts.VariableDeclaration, identifier: string): boolean {
        let initializer = declaration.initializer;
        if (initializer && ts.isArrowFunction(initializer)) {
            let modifiers = initializer.modifiers;
            if (!modifiers || modifiers.length === 0) {
                return false;
            }
            for (let modifier of modifiers) {
                if (modifier.kind === ts.SyntaxKind.AsyncKeyword) {
                    return true;
                }
            }
        }
        return false;
    }

    private isPromiseOrThenable(node: ts.Expression): boolean {
        // 检查是否为 Promise 构造函数调用
        if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Promise') {
            return true;
        }
        if (!ts.isCallExpression(node)) {
            return false;

        }
        // 检查是否为 Promise 的实例方法调用
        if (ts.isPropertyAccessExpression(node.expression)) {
            const { expression, name } = node.expression;
            if (ts.isIdentifier(name) && name.text === 'then') {
                return true;
            }
            if (ts.isIdentifier(expression) && expression.text === 'Promise' &&
                ts.isIdentifier(name) && (name.text === 'resolve' || name.text === 'reject')) {
                return true;
            }
        }
        // 检查是否为 Promise 的静态方法调用
        if (ts.isIdentifier(node.expression) && (node.expression.text === 'Promise' || node.expression.text === 'fetch')) {
            return true;
        }
        // 如果以上都不满足，认为不是 Promise 或 thenable 对象
        return false;
    }


    private addIssueReportNode(line: number, startCol: number, filePath: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(line, startCol, startCol, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }

    private reportIssue(arkFile: ArkFile, originTsPosition: LineColPosition, keyword: string): void {
        let filePath = arkFile.getFilePath();
        let lineNum = originTsPosition.getLineNo();
        let startColum = originTsPosition.getColNo();
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(lineNum, startColum, startColum, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}